#!/usr/bin/env python3
"""
V22 Clip Generator — Creates 8 dynamic video clips from reconstructed screenshots.
Each clip: 5-8 seconds, 1080x1920, 30fps, with zoom/pan/highlights/cursor.
"""

import os
import numpy as np
from moviepy import (
    ImageClip, CompositeVideoClip, VideoClip, TextClip,
    transforms as tfx, vfx
)
from PIL import Image, ImageDraw, ImageFont

BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
IMAGES_DIR = os.path.join(BASE, "02_images")
CLIPS_DIR = os.path.join(BASE, "03_clips")
W, H = 1080, 1920
FPS = 30
FONT_REG = "C:/Windows/Fonts/msyh.ttc"
FONT_BOLD = "C:/Windows/Fonts/msyhbd.ttc"

os.makedirs(CLIPS_DIR, exist_ok=True)


def make_highlight_box(w, h, color, alpha=0.2, duration=0.5):
    """Create a semi-transparent highlight overlay."""
    arr = np.zeros((h, w, 4), dtype=np.uint8)
    r, g, b = color
    arr[:, :, 0] = r
    arr[:, :, 1] = g
    arr[:, :, 2] = b
    arr[:, :, 3] = int(255 * alpha)
    return ImageClip(arr, duration=duration)


def make_cursor(size=24, duration=1.0, color=(255, 255, 255)):
    """Create a cursor arrow overlay."""
    arr = np.zeros((size, size, 4), dtype=np.uint8)
    # Draw arrow shape
    points = [(2, 2), (size-4, size-10), (size//2, size//2), (size-10, size-4)]
    # Simple triangle cursor
    img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
    draw = ImageDraw.Draw(img)
    draw.polygon([(2, 2), (2, size-2), (size-4, size-4)], fill=(255, 255, 255, 200))
    draw.polygon([(2, 2), (2, size-2), (size-4, size-4)], outline=(0, 0, 0, 255))
    arr = np.array(img)
    return ImageClip(arr, duration=duration, is_mask=False)


def make_text_overlay(text, font_size=28, color=(255, 255, 255), bg_color=None, duration=2.0):
    """Create a text overlay clip."""
    try:
        txt_clip = TextClip(
            text=text,
            font=FONT_BOLD,
            font_size=font_size,
            color=f"rgb{color}",
            bg_color=f"rgb{bg_color}" if bg_color else None,
            size=(W * 0.8, None),
            method="caption",
            text_align="center"
        ).with_duration(duration)
        return txt_clip
    except Exception as e:
        print(f"  TextClip error: {e}")
        return None


# ============================================================
# CLIP 1: Terminal Problem — Highlight error lines
# ============================================================
def make_clip_01():
    """Terminal showing pipeline failure. 8s. Zoom into error lines."""
    img = ImageClip(os.path.join(IMAGES_DIR, "01_hook_terminal_problem.png"), duration=8)

    # Zoom effect: start wide, zoom into error section
    def zoom_effect(t):
        progress = t / 8.0
        # Start at 1.0, zoom to 1.2 over time, then back
        if progress < 0.6:
            z = 1.0 + 0.2 * (progress / 0.6)
        else:
            z = 1.2 - 0.1 * ((progress - 0.6) / 0.4)
        # Pan: start center, move up slightly to show error lines
        y_offset = -80 * (progress / 0.6) if progress < 0.6 else -80 + 40 * ((progress - 0.6) / 0.4)
        return tfx.affine_shear(z, z, 0, 0, y_offset)

    clip = img.image_transform(zoom_effect)

    # Red highlight box appearing on error lines at t=1s
    highlight_arr = np.zeros((80, 900, 4), dtype=np.uint8)
    highlight_arr[:, :, 0] = 239
    highlight_arr[:, :, 1] = 68
    highlight_arr[:, :, 2] = 68
    highlight_arr[:, :, 3] = 60
    highlight_clip = (
        ImageClip(highlight_arr, duration=4)
        .with_position((80, 480))
        .with_start(1.0)
        .with_duration(2.5)
    )

    # Blinking cursor at bottom
    cursor_clip = (
        make_text_overlay("█", font_size=24, color=(59, 202, 102), duration=8)
        .with_position((100, 1000))
    )

    return CompositeVideoClip([clip, highlight_clip], size=(W, H)).with_duration(8)


# ============================================================
# CLIP 2: Bad CSV — Table scrolling with red highlights
# ============================================================
def make_clip_02():
    """Bad CSV evidence. 7s. Scroll through rows, highlight empty cells."""
    img = ImageClip(os.path.join(IMAGES_DIR, "02_bad_csv_evidence.png"), duration=7)

    # Slow zoom in
    def zoom_effect(t):
        z = 1.0 + 0.05 * (t / 7.0)
        y_off = -30 * (t / 7.0)
        return tfx.affine_shear(z, z, 0, 0, y_off)

    clip = img.image_transform(zoom_effect)

    # Row highlight overlays — staggered appearance
    highlights = []
    for i, (y_pos, duration, start) in enumerate([
        (350, 2.0, 1.0),   # Row 1
        (398, 2.0, 1.5),   # Row 2
        (542, 2.0, 2.0),   # Row 5
        (590, 2.0, 2.5),   # Row 6
        (638, 2.0, 3.0),   # Row 7
    ]):
        h_arr = np.zeros((44, 900, 4), dtype=np.uint8)
        h_arr[:, :, 0] = 239
        h_arr[:, :, 1] = 68
        h_arr[:, :, 2] = 68
        h_arr[:, :, 3] = 50
        h_clip = (
            ImageClip(h_arr, duration=duration)
            .with_position((80, y_pos))
            .with_start(start)
        )
        highlights.append(h_clip)

    return CompositeVideoClip([clip] + highlights, size=(W, H)).with_duration(7)


# ============================================================
# CLIP 3: Quality Gate Code — Pan through code
# ============================================================
def make_clip_03():
    """Code review. 8s. Pan through code, highlight key sections."""
    img = ImageClip(os.path.join(IMAGES_DIR, "03_quality_gate_code.png"), duration=8)

    def zoom_effect(t):
        # Slow pan from top of code to bottom
        progress = t / 8.0
        z = 1.0 + 0.08 * progress
        y_off = -100 * progress
        return tfx.affine_shear(z, z, 0, (1-progress)*50, y_off)

    clip = img.image_transform(zoom_effect)

    # Highlight REQUIRED_FIELDS section
    rf_highlight = np.zeros((60, 500, 4), dtype=np.uint8)
    rf_highlight[:, :, 0] = 250
    rf_highlight[:, :, 1] = 204
    rf_highlight[:, :, 2] = 21
    rf_highlight[:, :, 3] = 50
    rf_clip = (
        ImageClip(rf_highlight, duration=2.0)
        .with_position((280, 270))
        .with_start(1.0)
    )

    # Highlight validate function signature
    vf_highlight = np.zeros((40, 400, 4), dtype=np.uint8)
    vf_highlight[:, :, 0] = 59
    vf_highlight[:, :, 1] = 130
    vf_highlight[:, :, 2] = 246
    vf_highlight[:, :, 3] = 50
    vf_clip = (
        ImageClip(vf_highlight, duration=2.5)
        .with_position((180, 460))
        .with_start(3.5)
    )

    # Highlight gate_score line
    gs_highlight = np.zeros((40, 450, 4), dtype=np.uint8)
    gs_highlight[:, :, 0] = 59
    gs_highlight[:, :, 1] = 202
    gs_highlight[:, :, 2] = 102
    gs_highlight[:, :, 3] = 50
    gs_clip = (
        ImageClip(gs_highlight, duration=2.0)
        .with_position((160, 970))
        .with_start(5.5)
    )

    return CompositeVideoClip([clip, rf_clip, vf_clip, gs_clip], size=(W, H)).with_duration(8)


# ============================================================
# CLIP 4: Terminal Success — Green success highlights
# ============================================================
def make_clip_04():
    """Terminal success. 6s. Zoom into PASSED section."""
    img = ImageClip(os.path.join(IMAGES_DIR, "04_fixed_terminal_success.png"), duration=6)

    def zoom_effect(t):
        progress = t / 6.0
        z = 1.15 - 0.15 * abs(progress - 0.5) * 2  # zoom in, then out slightly
        y_off = -60 * min(progress * 2, 1.0)
        return tfx.affine_shear(z, z, 0, 0, y_off)

    clip = img.image_transform(zoom_effect)

    # Green glow around PASSED text
    glow = np.zeros((60, 400, 4), dtype=np.uint8)
    glow[:, :, 0] = 59
    glow[:, :, 1] = 202
    glow[:, :, 2] = 102
    glow[:, :, 3] = 60
    glow_clip = (
        ImageClip(glow, duration=2.5)
        .with_position((340, 680))
        .with_start(2.0)
    )

    return CompositeVideoClip([clip, glow_clip], size=(W, H)).with_duration(6)


# ============================================================
# CLIP 5: TOP20 Table — Row highlighting
# ============================================================
def make_clip_05():
    """TOP20 table. 7s. Highlight gold/silver/bronze rows."""
    img = ImageClip(os.path.join(IMAGES_DIR, "05_top20_table.png"), duration=7)

    # Zoom into table area
    def zoom_effect(t):
        progress = t / 7.0
        z = 1.0 + 0.06 * progress
        y_off = -50 * progress
        return tfx.affine_shear(z, z, 0, 0, y_off)

    clip = img.image_transform(zoom_effect)

    # Highlight gold row (rank 1)
    gold = np.zeros((44, 860, 4), dtype=np.uint8)
    gold[:, :, 0] = 255; gold[:, :, 1] = 215; gold[:, :, 2] = 0
    gold[:, :, 3] = 40
    gold_clip = (
        ImageClip(gold, duration=3.0)
        .with_position((110, 290))
        .with_start(1.0)
    )

    # Silver row
    silver = np.zeros((44, 860, 4), dtype=np.uint8)
    silver[:, :, 0] = 192; silver[:, :, 1] = 192; silver[:, :, 2] = 192
    silver[:, :, 3] = 40
    silver_clip = (
        ImageClip(silver, duration=2.5)
        .with_position((110, 334))
        .with_start(2.5)
    )

    # Bronze row
    bronze = np.zeros((44, 860, 4), dtype=np.uint8)
    bronze[:, :, 0] = 205; bronze[:, :, 1] = 127; bronze[:, :, 2] = 50
    bronze[:, :, 3] = 40
    bronze_clip = (
        ImageClip(bronze, duration=2.0)
        .with_position((110, 378))
        .with_start(4.0)
    )

    return CompositeVideoClip([clip, gold_clip, silver_clip, bronze_clip], size=(W, H)).with_duration(7)


# ============================================================
# CLIP 6: Before-After Compare — Pan left to right
# ============================================================
def make_clip_06():
    """Before-After comparison. 7s. Pan from left (failed) to right (fixed)."""
    img = ImageClip(os.path.join(IMAGES_DIR, "06_before_after_compare.png"), duration=7)

    def zoom_effect(t):
        progress = t / 7.0
        z = 1.0 + 0.03 * progress
        # Pan from left panel to right panel
        if progress < 0.4:
            x_off = 0  # Looking at left
        elif progress < 0.6:
            x_off = -200 * ((progress - 0.4) / 0.2)  # Transition
        else:
            x_off = -200  # Looking at right
        return tfx.affine_shear(z, z, x_off, 0, 0)

    clip = img.image_transform(zoom_effect)

    return CompositeVideoClip([clip], size=(W, H)).with_duration(7)


# ============================================================
# CLIP 7: Pipeline Flow — Zoom through each box
# ============================================================
def make_clip_07():
    """Pipeline flow. 6s. Focus on each box in sequence."""
    img = ImageClip(os.path.join(IMAGES_DIR, "07_pipeline_flow.png"), duration=6)

    def zoom_effect(t):
        progress = t / 6.0
        if progress < 0.2:
            z = 1.0; x_off = 0; y_off = 0
        elif progress < 0.4:
            p = (progress - 0.2) / 0.2
            z = 1.0 + 0.4 * p; x_off = -50 * p; y_off = -50 * p
        elif progress < 0.6:
            p = (progress - 0.4) / 0.2
            z = 1.4 - 0.3 * p; x_off = -50 + 100 * p; y_off = -50
        else:
            p = (progress - 0.6) / 0.4
            z = 1.1 + 0.3 * p; x_off = 50; y_off = -50 + 100 * p
        return tfx.affine_shear(z, z, x_off, 0, y_off)

    clip = img.image_transform(zoom_effect)

    # Highlight gate box
    gate_highlight = np.zeros((180, 340, 4), dtype=np.uint8)
    gate_highlight[:, :, 0] = 255; gate_highlight[:, :, 1] = 159; gate_highlight[:, :, 2] = 28
    gate_highlight[:, :, 3] = 50
    gate_clip = (
        ImageClip(gate_highlight, duration=2.0)
        .with_position((60, 410))
        .with_start(2.0)
    )

    # Highlight top20 box
    top_highlight = np.zeros((180, 340, 4), dtype=np.uint8)
    top_highlight[:, :, 0] = 255; top_highlight[:, :, 1] = 215; top_highlight[:, :, 2] = 0
    top_highlight[:, :, 3] = 50
    top_clip = (
        ImageClip(top_highlight, duration=2.0)
        .with_position((620, 410))
        .with_start(4.0)
    )

    return CompositeVideoClip([clip, gate_clip, top_clip], size=(W, H)).with_duration(6)


# ============================================================
# CLIP 8: Final Decision Card — Zoom into key text
# ============================================================
def make_clip_08():
    """Final decision. 6s. Zoom into key insight sections."""
    img = ImageClip(os.path.join(IMAGES_DIR, "08_final_decision_card.png"), duration=6)

    def zoom_effect(t):
        progress = t / 6.0
        if progress < 0.5:
            z = 1.0 + 0.08 * (progress / 0.5)
            y_off = -40 * (progress / 0.5)
        else:
            p = (progress - 0.5) / 0.5
            z = 1.08 - 0.04 * p
            y_off = -40 + 20 * p
        return tfx.affine_shear(z, z, 0, 0, y_off)

    clip = img.image_transform(zoom_effect)

    # Glow on the key insight text
    insight_glow = np.zeros((60, 700, 4), dtype=np.uint8)
    insight_glow[:, :, 0] = 59; insight_glow[:, :, 1] = 202; insight_glow[:, :, 2] = 102
    insight_glow[:, :, 3] = 50
    insight_clip = (
        ImageClip(insight_glow, duration=2.5)
        .with_position((190, 490))
        .with_start(2.0)
    )

    return CompositeVideoClip([clip, insight_clip], size=(W, H)).with_duration(6)


# ============================================================
# MAIN
# ============================================================
CLIP_CONFIGS = [
    ("clip_01_hook", make_clip_01, 8),
    ("clip_02_bad_csv", make_clip_02, 7),
    ("clip_03_quality_gate", make_clip_03, 8),
    ("clip_04_success", make_clip_04, 6),
    ("clip_05_top20", make_clip_05, 7),
    ("clip_06_compare", make_clip_06, 7),
    ("clip_07_flow", make_clip_07, 6),
    ("clip_08_decision", make_clip_08, 6),
]


def main():
    clip_paths = []
    for name, maker_fn, duration in CLIP_CONFIGS:
        path = os.path.join(CLIPS_DIR, f"{name}.mp4")
        print(f"Generating {name} ({duration}s)...")
        try:
            clip = maker_fn()
            clip = clip.with_fps(FPS)
            clip.write_videofile(
                path,
                codec="libx264",
                audio=False,
                preset="fast",
                pixel_format="yuv420p",
                ffmpeg_params=["-crf", "23"],
                logger=None,
            )
            file_size = os.path.getsize(path) / (1024*1024)
            print(f"  -> Saved: {path} ({file_size:.1f} MB)")
            clip_paths.append(path)
        except Exception as e:
            print(f"  ERROR: {e}")
            import traceback
            traceback.print_exc()

    print(f"\nDone! {len(clip_paths)} clips generated in {CLIPS_DIR}")
    return clip_paths


if __name__ == "__main__":
    main()
